from .Object import *
import math

def leftList(objList:list, n:int) ->list:
    '''将列表循环左移n%len(objList)位'''
    n = n%len(objList)
    newObjList = objList[n:]
    newObjList.extend(objList[:n])
    return newObjList

def getLeftNum(indexList:list, maxIndex:int) -> int:
    '''
    根据提供的索引列表和最大索引，判断能否通过左移原始数据列表使得索引对应元素在原始列表中连续,且最小索引为0
    - indexList 索引列表
    - maxIndex 最大索引 maxIndex = len(原始数据列表)
    - return n
        - n=-1 无论如何左移都无法让索引对应的元素连续
        - n=0 索引对应的元素本身就是连续的
        - n>0 原始数据列表左移n位就可让索引对应元素在原始数据列表中连续
    - 实现原理
        - 若想通过左移方式使元素连续，那么这些元素之间只能有一处间隔，即索引中只有一处间隔
        - 搜先查找第一处的间隔位置n，若n==0则表示提供的索引列表本身就是连续的，返回-1
        - 若n!=0，将后面的索引转为负索引，并将所有的索引重新组合，再次判断是否连续。
        - 若第二次不连续，则表示索引中有多处间隔，无论如何左移都无法使索引列表对应元素连续，返回-1
        - 若第二次连续，则表示原列表左移n位后索引列表对应元素可连续，返回n
    '''
    if len(indexList) < 2:
        return -1
    indexList.sort()
    n = 0
    # 找到第一个索引不连续的位置
    for i in range(1,len(indexList)):
        if indexList[i] - indexList[i-1] > 1:
            n = i
            break
    if n == 0: 
        return indexList[0]
    else:
        maxIndex += 1
        ll = [i-maxIndex for i in indexList[n:]] # 将后半部分的索引转为负索引
        ll.extend([i for i in indexList[:n]])
        # 判断是否有不连续的地方
        for i in range(1,len(ll)):
            # 如果还有不连续的地方，说明
            if ll[i] - ll[i-1] > 1: 
                return -1
    return indexList[n]

def connectable(f1:PlyFace, f2:PlyFace, modelB=False) -> bool:
    '''
    判断两个面能否连接
    - n >= 0 可以连接，只需要f2左移n位
    - n == -1 不可连接
    '''
    if f1.T != f2.T or f1.T in 'oO' or f2.T in 'oO':
        return False
    # 只有两个面的center有两个坐标相同才可合并
    k1, k2 = getClassificationStd(f1, modelB), getClassificationStd(f2, modelB)
    if k1[1] != k2[1]:
        return False
    # 获取公共顶点的索引
    pubVerIndexList1, pubVerIndexList2 = getPubVerIndexes(f1, f2)
    pvn = len(pubVerIndexList1)
    if pvn < 2 or pvn ==len(f1) or pvn == f2:
        return False
    # 判断公共顶点是否连续
    tag1 = getLeftNum(pubVerIndexList1, len(f1)-1)
    tag2 = getLeftNum(pubVerIndexList2, len(f2)-1)
    if tag1==-1 or tag2==-1:
        return False
    return True

def getPubVerIndexes(f1:PlyFace, f2:PlyFace):
    indexList1, indexList2 = [], []
    append1, append2 = indexList1.append, indexList2.append
    for i1 in range(len(f1)):
        v = f1[i1]
        i2 = f2.index(v)
        if i2 != -1:
            append1(i1)
            append2(i2)
    return indexList1, indexList2

def getPubVerDict(f1:PlyFace, f2:PlyFace) -> dict:
    pvd = {}
    for v in f1:
        if f2.index(v) != -1:
            pvd[v] = v
    return pvd

def getPubVerList(f1:PlyFace, f2:PlyFace) ->list:
    '''获取两个面的公共顶点列表'''
    if len(f1) > len(f2):
        f1, f2 = f2, f1
    pubVerList = []
    append = pubVerList.append
    for v in f1:
        if f2.index(v) != -1:
            append(v)
    return pubVerList

def connectFace(f1:PlyFace, f2:PlyFace) ->PlyFace:
    '''连接前需要使用connectable进行检验，如果没有检验而直接传入不可合并的面会有不可预知的错误'''
    # 获取公共顶点索引列表
    pubVids1, pubVids2 = getPubVerIndexes(f1, f2)
    # 获取需要对面进行左移的次数
    ln1, ln2 = getLeftNum(pubVids1, len(f1)-1), getLeftNum(pubVids2, len(f2)-1)
    # 保证f1的第一个顶点不是公共顶点
    vl1 = leftList(f1.verList, ln1)
    # 保证f2的第一个顶点是公共顶点
    vl2 = leftList(f2.verList, ln2)
    # 获取重构面后的公共顶点索引
    l = len(pubVids1) - 1
    verList = vl1[l : ]
    verList.extend(vl2[l : ])
    return PlyFace(verList)

def optimizeModel(model:PlyModel, optimVer=True, switchMode=False):
    '''优化模型'''
    faceList = model.faceList.copy()
    # 创建一个用于存放已经合并的面的list
    print('modelA')
    newFaceList = optimizeFaceList(faceList, switchMode)
    print('modelB')
    newFaceList = optimizeFaceList(newFaceList, not switchMode)
    if optimVer:
        newFaceList = optimizeVertexes(newFaceList)
    newModel = PlyModel(newFaceList)
    return newModel

def optimizeVertexes(faceList:list):
    # 将所有重要的顶点加入到verDict中，并进行去重
    verDict = {}
    for f in faceList:
        edgeList = f.edgeList
        for i in range(-len(edgeList),0):
            e1, e2 = edgeList[i], edgeList[i+1]
            ta, tb = e1.vector.cosA(e2.vector), 0.26
            if ta < tb and ta > -tb:
                v = e1.endVer
                verDict[v] = v
    # 遍历所有的面，并使用重要顶点进行重构
    newFaceList = []
    for f in faceList:
        verList = []
        for v in f:
            if v in verDict:
                verList.append(v)
        newFaceList.append(PlyFace(verList))
    return newFaceList

def optimizeFaceList(faceList:list, switchMode=False):
    newFaceList = []
    predDict = preteatmentFaces(faceList, switchMode)
    i, l =0, len(predDict)
    for k in predDict:
        i += 1
        a = len(predDict[k])
        opedFaceList = optimizeFaces(predDict[k], switchMode)
        print(f'{i}/{l}: {a} -> {len(opedFaceList)}')
        newFaceList.extend(opedFaceList)
    return newFaceList

def getVerXYZ(v:PlyVertex, attrName:str):
    if attrName == 'x':
        return v.x
    elif attrName == 'y':
        return v.y
    elif attrName == 'z':
        return v.z
    else:
        return None

def removePubEdges(faceList:list) -> dict:
    edgeDict = {}
    # 获取边的dict,并去除里边的公共边
    for f in faceList:                          # 遍历所有的面
        for e in f.edgeList:                    # 遍历面里的边
            tag = True                          
            for ee in edgeDict:                 # 检查edgeDict里是否有e的公共边
                if e.isPubEdge(ee) < 0:         # <0则代表e与ee是反向公共边，在合并时互相抵消
                    edgeDict.pop(ee)            # 从edgeDict中删除ee
                    tag = False                 
                    break                       # 跳出循环，检查下一个边
            if tag:
                edgeDict[e] = e                    # 若不是公共边则将e加入edgeDict
    return edgeDict

def edges2face(edgeDict:dict):
    verList = [list(edgeDict)[0].startVer]
    while True:
        tag = False
        for e in edgeDict:
            if e.startVer == verList[-1] and e.endVer != verList[0]:
                verList.append(e.endVer)
                tag = True
        if tag == False:
            return PlyFace(verList)

        

def optimizeFaces(faceList:list, modelB=False):
    if(len(faceList)) < 2:
        return faceList
    faceList.sort()
    newFaceList = []
    f1 = faceList.pop(0)
    tag = True
    while True:
        tag = False
        for f2 in faceList:
            if connectable(f1, f2, modelB):
                f1 = connectFace(f1, f2)
                faceList.remove(f2)
                tag = True
        if tag == False:
            newFaceList.append(f1)
            if len(faceList) == 0:
                break
            else:
                f1 = faceList.pop(0)
    
    return newFaceList
                        
def getClassificationStd(f:PlyFace, modelB=False):
    '''获取面在分类优化过程中的具体依据
    - 返回值 属性名称，属性值
    '''
    according = ['x','y','z']
    key = f.T
    attrName = key.lower()
    if attrName == 'o':
        return key, 'o'
    else:
        i = according.index(attrName)  -2
        attrName = according[i+1 if modelB else i]
        return attrName, getVerXYZ(f.center, attrName)

def preteatmentFaces(faceList:list, modelB=False):
    # 判断重新整理的依据
    faceDict = {}
    # 根据中心坐标将面重新整理成多个列表
    for f in faceList:
        key = f.T.lower()
        if key !='o':
            key1 = f.T,getVerXYZ(f.center, key)
            key2 = getClassificationStd(f, modelB)
            key = (key1, key2)
        if key in faceDict:
            faceDict[key].append(f)
        else:
            faceDict[key] = [f]
    return faceDict



def load(filePath):
    file = open(filePath, 'r')
    fileContent = [i[0:-1] for i in file.readlines()]
    file.close()
    # 从读取到的Content中获取顶点数和面数
    verNum, faceNum = -1,-1
    verStart = -1
    for i in range(len(fileContent)):
        s = fileContent[i]
        if 'element vertex' in s:
            verNum = int(s.split(' ')[-1])
        elif 'element face' in s:
            faceNum = int(s.split(' ')[-1])
        elif 'end_header' in s:
            verStart = i+1
            break
    
    # 根据得到的数据分解顶点列表和面列表
    verEnd = verStart + verNum
    venewObjListist = []
    for s in fileContent[verStart: verEnd]:
        t = s.split(' ') # [x, y, z, ……]
        venewObjListist.append(PlyVertex(t[0], t[1], t[2]))

    faceStart = verEnd
    faceEnd = faceStart + faceNum
    faceList = []
    for s in fileContent[faceStart : faceEnd]:
        t = s.split(' ')[1:]
        vl = [venewObjListist[int(vid)] for vid in t]
        faceList.append(PlyFace(vl))
    return PlyModel(faceList)


def save(model:PlyModel, fileName = 'Test.ply'):
    hz = '.ply'
    if not(hz in fileName) :
        fileName = fileName + hz
    file = open(fileName, 'w')
    # 写入ply的header
    file.write('ply\n')
    file.write('format ascii 1.0\n')
    file.write('comment : critipan\n')
    file.write(f'element vertex {len(model.verList)}\n')
    file.write('property float x\n')
    file.write('property float y\n')
    file.write('property float z\n')
    file.write(f'element face {len(model)}\n')
    file.write('property list uchar int vertex_index\nend_header\n')

    verList = model.verList.copy()
    verList.sort()
    verDict = {}
    print('写入顶点')
    for i in range(len(verList)):
        v = verList[i]
        verDict[v] = i
        sv = str(v)
        file.write(sv+'\n')
    print('写入面')
    for f in model:
        s = str(len(f))
        for v in f:
            s += f' {verDict[v]}'
        s += '\n'
        file.write(s)
    file.close()
    print('保存成功')